<?php defined('SYSPATH') or die('No direct script access.');
/**
 * 文件上传处理
 *
 * @author  Yellow.Chow <aultoale@gmail.com>
 * @version $Id: upload.php 187 2012-05-04 01:37:01Z yellow.chow $
 * @license http://www.gnu.org/licenses/gpl-3.0.html    GPL 3
 */
class Upload {

    /**
     * 工厂方法创建上传类
     *
     * @param  array  $config
     * @return Upload
     */
    public static function factory(array $config = array())
    {
        return new Upload($config);
    }

    /**
     * 图片文件扩展名类型
     *
     * @var array
     */
    protected $_imgs = array('jpg', 'jpeg', 'gif', 'png', 'bmp', 'wbmp');

    /**
     * 上传配置
     *
     * @var array
     */
    protected $_config = array(
        'exts'   => array('jpg', 'jpeg', 'bmp', 'wbmp', 'gif', 'png'),
        'size'   => 0, // KB, 0 则不限制
        'path'   => UPDPATH,
        'rename' => NULL, // 重命名接口名称&参数，例: array('md5', array('level' => 2, ...))
    );

    /**
     * 上传结果
     *
     * @var array
     */
    protected $_result = array();

    /**
     * 构造方法
     *
     * @param  array  $config
     */
    protected function __construct(array $config = array())
    {
        if ($config)
        {
            $this->set($config);
        }
    }

    /**
     * 设置上传配置
     *
     * @param  array  $config
     * @return Upload
     */
    public function set(array $config)
    {
        foreach ($config as $method => $val)
        {
            $method = 'set_'.$method;
            if (method_exists($this, $method))
            {
                $this->$method($val);
            }
        }

        return $this;
    }

    /**
     * 设置/获取允许上传的扩展名
     *
     * @param  array  $exts
     * @return Upload
     */
    public function set_exts(array $exts)
    {
        $this->_config['exts'] = $exts;

        return $this;
    }

    /**
     * 设置/获取允许上传的文件大小
     *
     * @param  int  $size
     * @return Upload
     */
    public function set_size($size)
    {
        $this->_config['size'] = (int) $size;

        return $this;
    }

    /**
     * 设置/获取文件保存路径
     *
     * @param  string  $path
     * @param  bool    $mkdir
     * @return Upload
     * @throws Upload_Exception
     */
    public function set_path($path, $mkdir = TRUE)
    {
        if ( ! is_dir($path) AND ! $mkdir)
        {
            throw new Upload_Exception("文件存储路径不存在 ':path'",
                array(':path' => $path));
        }

        $this->_config['path'] = IO::strip(rtrim($path, '/'));

        return $this;
    }

    /**
     * 设置/获取文件保存重命名接口名称
     *
     * @param  string  $rename
     * @return Upload
     * @throws Upload_Exception
     */
    public function set_rename($rename)
    {
        $class = 'Upload_'.ucfirst($rename);
        if ( ! class_exists($class))
        {
            throw new Upload_Exception("文件重命名接口不存在 ':rename'",
                array(':rename' => $rename));
        }

        $this->_config['rename'] = $rename;

        return $this;
    }

    /**
     * 获取文件扩展名
     *
     * @param  string  $filename
     * @return string
     */
    public function get_ext($filename)
    {
        return strtolower(substr(strrchr($filename, '.'), 1));
    }

    /**
     * 验证是否 POST 的文件上传数据
     *
     * // $upload->is_post($_FILES['file']);
     *
     * @param  array  $data
     * @return bool
     */
    public function is_post(array $data)
    {
        return isset($data['name']) AND isset($data['tmp_name']) AND isset($data['type']) AND isset($data['size']);
    }

    /**
     * 是否图片文件
     *
     * @param  string  $filename
     * @return bool
     */
    public function is_image($filename)
    {
        return in_array($this->get_ext(basename($filename)), $this->_imgs);
    }

    /**
     * 保存上传的文件
     *
     * <example>
     * $upload->save($_FILES['file']);
     * </example>
     *
     * @param  array   $data
     * @return array
     * @throws Upload_Exception
     */
    public function save(array $data)
    {
        if ( ! $this->is_post($data))
        {
            throw new Upload_Exception('无效的 POST 上传数据');
        }

        if (isset($data['error']) AND $data['error'] != 0)
        {
            throw new Upload_Exception($this->error($data['error']));
        }

        // 获取文件扩展名
        $ext = $this->get_ext($data['name']);

        if ( ! in_array($ext, $this->_config['exts']))
        {
            throw new Upload_Exception('禁止上传该类型的文件 ".:ext"',
                array(':ext' => $ext));
        }

        if ($this->_config['size'] AND  $data['size'] > $this->_config['size'] * 1024)
        {
            throw new Upload_Exception("文件大小超出服务器限定的值 :size Kb",
                array(':size' => $this->_config['size']));
        }

        if ($this->_config['rename']) // 重命名上传
        {
            $class = 'Upload_'.ucfirst($this->_config['rename']);
            $object = new $class($data['tmp_name']);
            if ( ! $object instanceof Upload_Rename)
            {
                throw new Upload_Exception("无效的文件上传重命名接口 :class",
                    array(':class' => $class));
            }

            $filename = $object->save($this->_config['path'], $data['name']);
        }
        else // 常规上传
        {
            if ( ! IO::mkdir($this->_config['path']))
            {
                throw new Upload_Exception("无法创建文件上传目录 :path",
                    array(':path' => $this->_config['path']));
            }

            $filename = IO::strip($this->_config['path'].'/'.$data['name']);
            if ( ! @move_uploaded_file($data['tmp_name'], $filename))
            {
                throw new Upload_Exception('无法从临时目录复制上传文件');
            }
        }

        $this->_result = array(
            'name' => $data['name'],
            'size' => $data['size'],
            'type' => $data['type'],
            'ext'  => $ext,
            'file' => $filename,
        );

        return $this->_result;
    }

    /**
     * 获取文件上传的结果
     *
     * @return array
     */
    public function result()
    {
        return $this->_result;
    }

    /**
     * 根据上传错误代码获取相应的文件信息
     *
     * @param  int  $errno
     * @return string
     */
    public function error($errno)
    {
        switch ($errno)
        {
            case  1 : return '上传的文件大小超出服务器限定的值';
            case  2 : return '文件大小超出 HTML 控件限定的值';
            case  3 : return '文件只有部分被上传';
            case  4 : return '没有文件被上传';
            case  5 : return '上传失败，原因未知';
            case  6 : return '找不到临时文件夹';
            case  7 : return '文件写入失败';
            default : return '未知错误';
        }
    }

}